﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.IO
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    /// <summary>Presents a section of an underlying <see cref="Stream"/> object as a read-only <see cref="Stream"/>.
    /// </summary>
    /// <threadsafety static="true" instance="false"/>
    public sealed class SegmentStream : NonSeekableStream
    {
        private Stream stream;
        private long remaining;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="SegmentStream"/> class by calling
        /// <see cref="SegmentStream(Stream, long, long)">SegmentStream</see>(<paramref name="stream"/>,
        /// 0L, <see cref="long.MaxValue"/>).</summary>
        public SegmentStream(Stream stream) : this(stream, 0L, long.MaxValue)
        {
        }

        /// <summary>Initializes a new instance of the <see cref="SegmentStream"/> class.</summary>
        /// <param name="stream">The underlying stream.</param>
        /// <param name="offset">The position in the underlying stream at which reading starts.</param>
        /// <param name="count">The maximum number of bytes to be read.</param>
        /// <exception cref="ArgumentException"><paramref name="stream"/>.<see cref="Stream.CanSeek"/> is <c>false</c>.
        /// </exception>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and/or <paramref name="count"/> are
        /// negative.</exception>
        public SegmentStream(Stream stream, long offset, long count)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (!stream.CanSeek)
            {
                throw new ArgumentException("CanSeek must be true.", "stream");
            }

            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("offset", ExceptionMessages.NonnegativeNumberRequired);
            }

            if (count < 0)
            {
                throw new ArgumentOutOfRangeException("count", ExceptionMessages.NonnegativeNumberRequired);
            }

            this.stream = stream;
            this.stream.Seek(offset, SeekOrigin.Begin);
            this.remaining = count;
        }

        /// <summary>See <see cref="Stream.CanRead"/>.</summary>
        public sealed override bool CanRead
        {
            get { return (this.stream != null) && this.stream.CanRead; }
        }

        /// <summary>See <see cref="Stream.CanWrite"/>.</summary>
        /// <value>Always <c>false</c>, even if the underlying stream supports writing.</value>
        public sealed override bool CanWrite
        {
            get { return false; }
        }

        /// <summary>See <see cref="Stream.Flush"/>.</summary>
        public sealed override void Flush()
        {
            this.AssertNotDisposed();
        }

        /// <summary>See <see cref="Stream.Read(byte[], int, int)"/>.</summary>
        public sealed override int Read(byte[] buffer, int offset, int count)
        {
            this.AssertNotDisposed();
            var result = this.stream.Read(buffer, offset, (int)Math.Min(this.remaining, count));
            this.remaining -= result;
            return result;
        }

        /// <summary>See <see cref="Stream.Write(byte[], int, int)"/>.</summary>
        /// <exception cref="NotSupportedException">Thrown with each call.</exception>
        public sealed override void Write(byte[] buffer, int offset, int count)
        {
            throw CreateNotSupportedException();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Stream.Dispose(bool)"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Dispose() must never throw.")]
        protected sealed override void Dispose(bool disposing)
        {
            try
            {
                if (disposing && (this.stream != null))
                {
                    this.stream.Dispose();
                }
            }
            catch
            {
            }
            finally
            {
                this.stream = null;
                base.Dispose(disposing);
            }
        }
    }
}
